package po.tree;

import po.entity.Indicator;
import po.entity.Operatable;
import po.entity.Rule;
import po.enums.Operator;
import po.exception.SystemDefinedException;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by guowei on 2019/6/9.
 */
public class Tree {
    private String ID; // reference to root-Rule ID
    private List<List<Node>> levels;
    private static ConcurrentHashMap<Position, ResultNode> resultNodeMap;  //initalize when class preparation
    public Tree(Rule root) {
        // use DFS to construct `levels`
        this.ID = root.getID();
        // currently, we consider the `root` must be a `Rule` type
        this.levels = new ArrayList<>();
        resultNodeMap = new ConcurrentHashMap<>();
        this.constructLevels(root, 0);
    }

    public List<List<Node>> getLevels() {
        return levels;
    }

    public static ConcurrentHashMap<Position, ResultNode> getResultNodeMap() {
        return resultNodeMap;
    }

    public static void setResultNode(Position pos, ResultNode rn) {
        resultNodeMap.put(pos, rn);
    }

    // checkConflicts: true -> there is conflicts, false -> no conflicts
    public boolean checkConflicts() throws Exception {
        for(int i=this.levels.size()-1;i>=0;i--) {
            List<Node> currentLevel = this.levels.get(i);
            for(int j=0;j<currentLevel.size();j++) {
                Node cursor = currentLevel.get(j);
                ResultNode rn = new ResultNode(cursor.getPosition());
                try {
                    cursor.getOperatable().operate(rn, Operator.DEFAULT);
                } catch (Exception e) {
                    if(!(e instanceof SystemDefinedException)) {
                        throw e;
                    } else {
                        System.out.println(e.getMessage());
                        return true;
                    }
                }
                resultNodeMap.put(cursor.getPosition(), rn);
            }
        }
        System.out.println(resultNodeMap.get(new Position(0,0)));
        return false;
    }

    private void constructLevels(Rule rule, int level) {
        // DFS
        // only Rule type has children, so meet no-subRules Rule or Indicator, just return

        // set a new List to specific level if needed
        if(level >= this.levels.size()) {
            this.levels.add(new ArrayList<>());
        }
        List<Node> currentLevel = this.levels.get(level);
        // construct Node
        Node n = new Node(rule.getID(), rule, level, currentLevel.size());
        // set Node value to Rule
        rule.setNode(n);
        // add Node to current level session
        currentLevel.add(n);

        // deal with subRules
        if(rule.getSubRules().size() == 0) {
            return;
        } else {
            List<Operatable> subRules = rule.getSubRules();
            for(int i=0;i<subRules.size();i++) {
                Operatable operatable = subRules.get(i);
                switch (operatable.getType()) {
                    case RULE:
                        this.constructLevels((Rule)operatable, level+1);
                        break;
                    case INDICATOR:
                        Indicator sub = (Indicator)operatable;
                        // indicator should be next level node, so there should calculate level+1 session
                        // same as above "add Node" part logic
                        if(level+1 >= this.levels.size()) {
                            this.levels.add(new ArrayList<>());
                        }
                        List<Node> nextLevelNodes = this.levels.get(level+1);
                        Node indicatorNode = new Node(sub.getID(), sub, level+1, nextLevelNodes.size());
                        sub.setNode(indicatorNode);
                        nextLevelNodes.add(indicatorNode);
                        break;
                }
            }
        }
    }
}
